﻿using JESAI.ClamAV.NetScannerCore.Command;
using JESAI.ClamAV.NetScannerCore.Configuration;
using JESAI.ClamAV.NetScannerCore.Enums;
using JESAI.ClamAV.NetScannerCore.Exceptions;
using JESAI.ClamAV.NetScannerCore.Models;
using JESAI.ClamAV.NetScannerCore.ScanResults;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace JESAI.ClamAV.NetScannerCore
{
    public class ClamAVScannerClient: IClamAVScannerClient
    {
        /// <summary>
        /// 发送到ClamAV服务器时，流将被分解到的最大大小（字节）。在SendAndScanFile方法中使用。默认大小为128kb。
        /// </summary>
        public int MaxChunkSize { get; set; }

        /// <summary>
        /// 在ClamAV服务器终止连接之前，可以传输到该服务器的最大大小（字节）。在SendAndScanFile方法中使用。默认大小为25mb。
        /// </summary>
        public long MaxStreamSize { get; set; }
        private ICommandExecutor CommandExecutor;
        private readonly IClamAvScannerConfiguration ClamAvScannerConfiguration;

        public ClamAVScannerClient(ICommandExecutor commandExecutor, IClamAvScannerConfiguration clamAvScannerConfiguration)
        {
            CommandExecutor = commandExecutor;
            ClamAvScannerConfiguration = clamAvScannerConfiguration;
            this.MaxChunkSize = clamAvScannerConfiguration.MaxChunkSize;
            this.MaxStreamSize = clamAvScannerConfiguration.MaxStreamSize;
        }
        private async Task SendStreamFileChunksAsync(Stream sourceStream, Stream clamStream)
        {
            var size = this.MaxChunkSize;
            var bytes = new byte[size];

            while ((size = await sourceStream.ReadAsync(bytes, 0, size)) > 0)
            {
                if (sourceStream.Position > this.MaxStreamSize)
                {
                    throw new StreamSizeOverFlowException(this.MaxStreamSize);
                }

                var sizeBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(size));

                await clamStream.WriteAsync(sizeBytes, 0, sizeBytes.Length);
                await clamStream.WriteAsync(bytes, 0, size);
            }

            var end = BitConverter.GetBytes(0);
            await clamStream.WriteAsync(end, 0, end.Length);
        }
        /// <summary>
        /// Helper方法将字节数组通过连接发送到ClamAV服务器，分成块。
        /// </summary>
        /// <param name="sourceStream">要发送到ClamAV服务器的流。</param>
        /// <param name="clamStream">到ClamAV服务器的通信通道。</param>
        /// <param name="cancellationToken"></param>
        private async Task SendStreamFileChunksAsync(Stream sourceStream, Stream clamStream, CancellationToken cancellationToken)
        {
            var size = MaxChunkSize;
            var bytes = new byte[size];

            while ((size = await sourceStream.ReadAsync(bytes, 0, size, cancellationToken).ConfigureAwait(false)) > 0)
            {
                if (sourceStream.Position > MaxStreamSize)
                {
                    throw new StreamSizeOverFlowException(MaxStreamSize);
                }

                var sizeBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(size));  //convert size to NetworkOrder!
                await clamStream.WriteAsync(sizeBytes, 0, sizeBytes.Length, cancellationToken).ConfigureAwait(false);
                await clamStream.WriteAsync(bytes, 0, size, cancellationToken).ConfigureAwait(false);
            }

            var newMessage = BitConverter.GetBytes(0);
            await clamStream.WriteAsync(newMessage, 0, newMessage.Length, cancellationToken).ConfigureAwait(false);
        }

        /// <summary>
        /// 获取ClamAV服务器版本
        /// </summary>
        public Task<string> GetVersionAsync()
        {
            return GetVersionAsync(CancellationToken.None);
        }

        /// <summary>
        /// 获取ClamAV服务器版本
        /// </summary>
        /// <param name="cancellationToken">用于请求的取消令牌</param>
        public async Task<string> GetVersionAsync(CancellationToken cancellationToken)
        {
            var version = await CommandExecutor.ExecuteAsync(ClamAVCommand.VERSION, cancellationToken).ConfigureAwait(false);

            return version;
        }

        /// <summary>
        /// 在ClamAV服务器上执行PING命令。
        /// </summary>
        /// <returns>如果服务器以PONG响应，则返回true。否则返回false。</returns>
        public Task<bool> PingAsync()
        {
            return PingAsync(CancellationToken.None);
        }

        /// <summary>
        /// 在ClamAV服务器上执行PING命令。
        /// </summary>
        /// <param name="cancellationToken">用于请求的取消令牌</param>
        /// <returns>如果服务器以PONG响应，则返回true。否则返回false。</returns>
        public async Task<bool> PingAsync(CancellationToken cancellationToken)
        {
            var result = await CommandExecutor.ExecuteAsync(ClamAVCommand.PING, cancellationToken).ConfigureAwait(false);
            return result.ToLowerInvariant() == "pong";
        }

        /// <summary>
        /// 扫描ClamAV服务器上的文件/目录。
        /// </summary>
        /// <param name="filePath">ClamAV服务器上文件/目录的路径。</param>
        public Task<ScannerResult> ScanFileOnServerAsync(string filePath)
        {
            var result = ScanFileOnServerAsync(filePath, CancellationToken.None);          
            return result;
        }

        /// <summary>
        /// 扫描ClamAV服务器上的文件/目录。
        /// </summary>
        /// <param name="filePath">ClamAV服务器上文件/目录的路径。</param>
        /// <param name="cancellationToken">用于请求的取消令牌</param>
        public async Task<ScannerResult> ScanFileOnServerAsync(string filePath, CancellationToken cancellationToken)
        {
            return new ClamAVScannerResult(await CommandExecutor.ExecuteAsync(String.Format(ClamAVCommand.SCAN+" {0}", filePath), cancellationToken).ConfigureAwait(false)).ToScanResult();
        }


        /// <summary>
        /// 使用服务器上的多个线程扫描ClamAV服务器上的文件/目录。
        /// </summary>
        /// <param name="filePath">ClamAV服务器上文件/目录的路径。</param>
        public Task<ScannerResult> ScanFileOnServerMultithreadedAsync(string filePath)
        {
            return ScanFileOnServerMultithreadedAsync(filePath, CancellationToken.None);
        }

        /// <summary>
        /// 使用服务器上的多个线程扫描ClamAV服务器上的文件/目录。
        /// </summary>
        /// <param name="filePath">ClamAV服务器上文件/目录的路径。</param>
        /// <param name="cancellationToken">用于请求的取消令牌</param>
        public async Task<ScannerResult> ScanFileOnServerMultithreadedAsync(string filePath, CancellationToken cancellationToken)
        {
            return new ClamAVScannerResult(await CommandExecutor.ExecuteAsync(String.Format(ClamAVCommand.MULTISCAN+ " {0}", filePath), cancellationToken).ConfigureAwait(false)).ToScanResult();
        }

        /// <summary>
        /// 将数据作为流发送到ClamAV服务器。
        /// </summary>
        /// <param name="fileData">包含文件数据的字节数组。</param>
        /// <returns></returns>
        public async Task<ScannerResult> SendAndScanFileAsync(byte[] fileData)
        {
            using (var sourceStream = new MemoryStream(fileData))
            {
                var result = new ClamAVScannerResult(await this.CommandExecutor.ExecuteAsync(ClamAVCommand.INSTREAM,
                    (stream) => this.SendStreamFileChunksAsync(sourceStream, stream)));
                return result.ToScanResult();
            }
        }
        /// <summary>
        /// 将数据作为流发送到ClamAV服务器。
        /// </summary>
        /// <param name="bytes">包含数据的字节数组。</param>
        /// <returns></returns>
        public async Task<ScannerResult> SendAndScanBytesAsync(byte[] bytes)
        {
            using (var sourceStream = new MemoryStream(bytes))
            {
                var result = new ClamAVScannerResult(await this.CommandExecutor.ExecuteAsync(ClamAVCommand.INSTREAM,
                    (stream) => this.SendStreamFileChunksAsync(sourceStream, stream)));
                return result.ToScanResult();
            }
        }

        /// <summary>
        /// 将数据作为流发送到ClamAV服务器。
        /// </summary>
        /// <param name="fileData">包含文件数据的字节数组。</param>
        /// <param name="cancellationToken">用于请求的取消令牌</param>
        /// <returns></returns>
        public async Task<ScannerResult> SendAndScanFileAsync(byte[] fileData, CancellationToken cancellationToken)
        {
            var sourceStream = new MemoryStream(fileData);
            return new ClamAVScannerResult(await CommandExecutor.ExecuteAsync(ClamAVCommand.INSTREAM, cancellationToken, (stream, token) => SendStreamFileChunksAsync(sourceStream, stream, token)).ConfigureAwait(false)).ToScanResult();
        }

        /// <summary>
        /// 将数据作为流发送到ClamAV服务器。
        /// </summary>
        /// <param name="sourceStream">包含要扫描的数据的流。</param>
        /// <returns></returns>
        public async Task<ScannerResult> SendAndScanFileAsync(Stream sourceStream)
        {
            return new ClamAVScannerResult(await CommandExecutor.ExecuteAsync(ClamAVCommand.INSTREAM,(stream) => SendStreamFileChunksAsync(sourceStream, stream))).ToScanResult();
        }

        /// <summary>
        /// 将数据作为流发送到ClamAV服务器。
        /// </summary>
        /// <param name="sourceStream">包含要扫描的数据的流。</param>
        /// <param name="cancellationToken">用于请求的取消令牌</param>
        /// <returns></returns>
        public async Task<ScannerResult> SendAndScanFileAsync(Stream sourceStream, CancellationToken cancellationToken)
        {
            return new ClamAVScannerResult(await CommandExecutor.ExecuteAsync(ClamAVCommand.INSTREAM, cancellationToken, (stream, token) => SendStreamFileChunksAsync(sourceStream, stream, token)).ConfigureAwait(false)).ToScanResult();
        }

        /// <summary>
        /// 从路径读取文件，然后将其作为流发送到ClamAV服务器。
        /// </summary>
        /// <param name="filePath">文件/目录的路径。</param>
        public async Task<ScannerResult> SendAndScanFileAsync(string filePath)
        {
            using (var stream = File.OpenRead(filePath))
            {
                return await SendAndScanFileAsync(stream).ConfigureAwait(false);
            }
        }

        /// <summary>
        /// 从路径读取文件，然后将其作为流发送到ClamAV服务器。
        /// </summary>
        /// <param name="filePath">文件/目录的路径。</param>
        /// <param name="cancellationToken">用于请求的取消令牌</param>
        public async Task<ScannerResult> SendAndScanFileAsync(string filePath, CancellationToken cancellationToken)
        {
            using (var stream = File.OpenRead(filePath))
            {
                return await SendAndScanFileAsync(stream, cancellationToken).ConfigureAwait(false);
            }
        }
    }
}
